package com.scarecrow.olga.game.object {
	
	import com.scarecrow.olga.entity.Entity;
	import com.scarecrow.olga.error.VerboseError;
	import com.scarecrow.olga.game.component.GameComponent;
	import com.scarecrow.olga.game.ComponentManager;
	import com.scarecrow.olga.game.object.event.GameObjectEvent;
	import com.scarecrow.olga.util.logging.Logger;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	
	[Event(name = "componentAdded", type = "com.scarecrow.olga.game.object.event.GameObjectEvent")]
	
	[Event(name = "componentRemoved", type = "com.scarecrow.olga.game.object.event.GameObjectEvent")]
	
	/**
	 * The GameObject class is intended to be the centerpiece of information for a game. 
	 * It provides functions and properties to work as the hubs and containers of 
	 * GameComponents, which will be the building blocks of the game.
	 * 
	 * This class is not supposed to be Inherited because all business logic should go
	 * to GameComponents instead.
	 * ...
	 * @author Santiago.J.Elizalde
	 */
	public final class GameObject extends Entity {
		
		private var properties:Object;
		private var components:Dictionary;
		private var _tags:Array;
		
		private var _eventBroadcaster:IEventDispatcher;
		
		public function GameObject(name:String = "") {
			this._eventBroadcaster = new EventDispatcher();
			this.properties = new Object();
			this.components = new Dictionary();
			super(name);
		}
		
		/**
		 * Tells wether a property with the supplied key has a value assigned (including null or "")
		 * in this game object.
		 * 
		 * @param	key
		 * @return	true if the property exists, false otherwise.
		 */
		public final function hasProp(key:String):Boolean {
			return key in properties;
		}
		
		/**
		 * Retrieves a property from this gameObject using the supplied key. can and will return 
		 * 'undefined' if there is no such property in the gameObject.
		 * 
		 * @param	key the key to the property.
		 * @return	the value stored with the supllied key.
		 */
		public final function getProp(key:String):* {
			return properties[key];
		}
		
		/**
		 * Stores the supplied value under the supplied key. Set values can be retrieved using the
		 * getProp() method.
		 * 
		 * @param	key		The key which the property will be assigned.
		 * @param	value	The value to store on the supplied key.
		 */
		public final function setProp(key:String, value:*):void {
			properties[key] = value;
		}
		
		/**
		 * Tells wether this gameObject has a component of the supplied type (Class).
		 * 
		 * @param	type	the class of the component that needs to be checked.
		 * @return	true	if the component is found, false otherwise.
		 */
		public final function hasComponent(type:Class):Boolean {
			return type in components;
		}
		
		/**
		 * Retrieves a component from the GameObject if one exists with the supplied type(Class).
		 * 
		 * @param	type	The Class of the desired component.
		 * @return	the component of the supplied type. will return null if the component is not present.
		 */
		public final function getComponent(type:Class):GameComponent {
			if (!type in components) {
				Logger.log("There is no component with type '" + type + "' registered in gameComponent '" + this.name + "'.", Logger.LOG_LEVEL_WARN);
				return null;
			}
			return components[type];
		}
		
		/**
		 * Adds a component to the GameObject. Only one component of each type is supported.
		 * adding two components of the same type will throw an error.
		 * 
		 * @param	component the component instance that needs to be added to the gameObject.
		 * @throws	error if there is another component of the same type registered.
		 */
		public final function addComponent(component:GameComponent):void {
			if (!component) {
				throw new VerboseError("Unable to add component.", "supplied component is NULL");
			}
			if (components[component.type]) {
				throw new VerboseError("Unable to add component.", "there is another component with the same class registered on the GameObject", {componentType:component.type})
			}
			this.components[component.type] = component;
			component.owner = this;
			ComponentManager.registerComponent(component);
			_eventBroadcaster.dispatchEvent(new GameObjectEvent(GameObjectEvent.COMPONENT_ADDED, this, component));
		}
		
		/**
		 * Removes the component of the supplied type from the gameObject.
		 * 
		 * @param	type the component class that needs to be removed from the gameObject.
		 */
		public final function removeComponent(type:Class):void {
			var deletedComp:GameComponent = this.components[type];
			if (!deletedComp) {
				throw new VerboseError("Unable to remove component from GameObject", "The GameObject does not contain a component of the specified type", {gameObject:this.name, componentType:type});
			}
			deletedComp.owner = null;
			delete this.components[type];
			ComponentManager.releaseComponent(deletedComp);
			_eventBroadcaster.dispatchEvent(new GameObjectEvent(GameObjectEvent.COMPONENT_REMOVED, this, deletedComp));
		}
		
		/**
		 * Releases the GameObject from the game. The GameObject is destroyed
		 * here and removed from all registries.
		 * 
		 * Any contained components are also removed in the process, and the
		 * COMPONENT_REMOVED event will be fired for each of them.
		 * 
		 * an OBJECT_RELEASED event will be fired when the GameObject is destroyed
		 * but not yet removed from the EntityRegistry(and thus it's ID becoming 
		 * invalid), this is done so that proper removal tracking is possible.
		 */
		override public function release():void {
			
			for each (var component:GameComponent in this.components) {
				removeComponent(component.type);
			}
			components = null;
			properties = null;
			_eventBroadcaster.dispatchEvent(new GameObjectEvent(GameObjectEvent.OBJECT_RELEASED, this, null));
			_eventBroadcaster = null;
			super.release();
		}
		
		/**
		 * Adds a GameObjectEvent listener of the specified type to this gameObject.
		 * All events added this way will be set with weak references in case the 
		 * gameObject is released and no handler for the OBJECT_RELEASED is implemented.
		 * 
		 * @param	eventType	the event type that will be fired.
		 * @param	handler		the function to call when the event type is triggered.
		 */
		public final function addEventListener(eventType:String, handler:Function):void {
			_eventBroadcaster.addEventListener(eventType, handler, false, 0, true);
		}
		
		/**
		 * Removes a previously added event listener of the specified type.
		 * 
		 * @param	eventType	the event type that was being listened.
		 * @param	handler		the handler function specified when addEventListener() was called
		 */
		public final function removeEventListener(eventType:String, handler:Function):void {
			_eventBroadcaster.removeEventListener(eventType, handler);
		}
	}

}